using System;
using System.Linq;
using System.Reflection;

namespace Awesome.Extensions.Reflection
{
    public static class GetMethodEx
    {
        #region GetMethod

        public static MethodInfo GetMethod<T1>(this Type type, string name,
                                               BindingFlags flags =
                                                   BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null, new[] { typeof(T1) }, null);
        }

        public static MethodInfo GetMethod<T1, T2>(this Type type, string name,
                                                   BindingFlags flags =
                                                       BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null, new[] { typeof(T1), typeof(T2) }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3>(this Type type, string name,
                                                       BindingFlags flags =
                                                           BindingFlags.Public | BindingFlags.Instance |
                                                           BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null, new[] { typeof(T1), typeof(T2), typeof(T3) }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4>(this Type type, string name,
                                                           BindingFlags flags =
                                                               BindingFlags.Public | BindingFlags.Instance |
                                                               BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null, new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5>(this Type type, string name,
                                                               BindingFlags flags =
                                                                   BindingFlags.Public | BindingFlags.Instance |
                                                                   BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6>(this Type type, string name,
                                                                   BindingFlags flags =
                                                                       BindingFlags.Public | BindingFlags.Instance |
                                                                       BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5), typeof(T6) },
                                  null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7>(this Type type, string name,
                                                                       BindingFlags flags =
                                                                           BindingFlags.Public | BindingFlags.Instance |
                                                                           BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7)
                                      }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7, T8>(this Type type, string name,
                                                                           BindingFlags flags =
                                                                               BindingFlags.Public |
                                                                               BindingFlags.Instance |
                                                                               BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7), typeof (T8)
                                      }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7, T8, T9>(this Type type, string name,
                                                                               BindingFlags flags =
                                                                                   BindingFlags.Public |
                                                                                   BindingFlags.Instance |
                                                                                   BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7), typeof (T8), typeof (T9)
                                      }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(this Type type, string name,
                                                                                    BindingFlags flags =
                                                                                        BindingFlags.Public |
                                                                                        BindingFlags.Instance |
                                                                                        BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7), typeof (T8), typeof (T9), typeof (T10)
                                      }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(this Type type, string name,
                                                                                         BindingFlags flags =
                                                                                             BindingFlags.Public |
                                                                                             BindingFlags.Instance |
                                                                                             BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7), typeof (T8), typeof (T9), typeof (T10), typeof (T11)
                                      }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(
            this Type type, string name,
            BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7)
                                          , typeof (T8), typeof (T9), typeof (T10), typeof (T11), typeof (T12)
                                      }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(this Type type,
                                                                                                   string name,
                                                                                                   BindingFlags flags =
                                                                                                       BindingFlags.Public |
                                                                                                       BindingFlags.Instance |
                                                                                                       BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7), typeof (T8), typeof (T9), typeof (T10), typeof (T11),
                                          typeof (T12),
                                          typeof (T13)
                                      }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>(this Type type,
                                                                                                        string name,
                                                                                                        BindingFlags
                                                                                                            flags =
                                                                                                            BindingFlags.Public |
                                                                                                            BindingFlags.Instance |
                                                                                                            BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7), typeof (T8), typeof (T9), typeof (T10), typeof (T11),
                                          typeof (T12),
                                          typeof (T13), typeof (T14)
                                      }, null);
        }

        public static MethodInfo GetMethod<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(
            this Type type, string name,
            BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        {
            return type.GetMethod(name, flags, null,
                                  new[]
                                      {
                                          typeof (T1), typeof (T2), typeof (T3), typeof (T4), typeof (T5), typeof (T6),
                                          typeof (T7), typeof (T8), typeof (T9), typeof (T10), typeof (T11),
                                          typeof (T12),
                                          typeof (T13), typeof (T14), typeof (T15)
                                      }, null);
        }

        #endregion

        private static TDelegate GetDelegateInternal<TDelegate>(this Type type, object target, string methodName)
            where TDelegate : class
        {
            const BindingFlags BINDING_FLAGS = BindingFlags.Instance | BindingFlags.NonPublic |
                                              BindingFlags.Public | BindingFlags.Static;

            var delegateType = typeof (TDelegate);
            if (!delegateType.IsSubclassOf<Delegate>())
            {
                throw new ArgumentException("TDelegate is not delegate");
            }

            var invokeMethod = delegateType.GetMethod("Invoke");

            var argTypes = invokeMethod.GetParameters().Select(p => p.ParameterType).ToArray();
            var retType = invokeMethod.ReturnType;

            var method = type.GetMethod(methodName, BINDING_FLAGS, null, argTypes, null);
            
            if (method == null || method.ReturnType != retType)
            {
                throw new ArgumentException("Method lookup fail");
            }
            
            if (!method.IsStatic && target == null)
                throw new ArgumentException("Target must be specified for nonstatic method");

            return Delegate.CreateDelegate(delegateType, target, method, false) as TDelegate;
        }


        public static TDelegate GetDelegate<TDelegate>(this Type type, string methodName)
            where TDelegate : class
        {
            return type.GetDelegateInternal<TDelegate>(null, methodName);
        }

        public static TDelegate GetDelegate<TDelegate>(this Type type, object target, string methodName)
            where TDelegate : class
        {
            if (target == null)
                throw new ArgumentNullException("target");

            return type.GetDelegateInternal<TDelegate>(target, methodName);
        }
    }
}